브라우저 기반 애플리케이션을 위한 Go와 WebAssembly
Olivia Novak
Dev Intern · Leapcell

소개: 브라우저에서 Go의 잠재력 발휘
웹 개발의 환경은 오랫동안 JavaScript, 강력하고 보편적인 언어에 의해 지배되어 왔습니다. 그러나 클라이언트 측에서 고성능, 타입 안전, 멀티 패러다임 개발에 대한 열망은 대안 솔루션에 대한 관심을 불러일으켰습니다. WebAssembly(Wasm)를 소개합니다. 웹 브라우저에서 안전하고 보안적으로 실행되도록 설계된 저수준 바이트코드 형식입니다. Wasm은 개발자가 다양한 프로그래밍 언어의 강점을 웹 플랫폼으로 가져올 수 있게 하는 매혹적인 새로운 지평을 열어줍니다. Go 개발자에게는 이것이 매우 흥미로운 기회를 제공합니다. Go의 동시성, 강력한 표준 라이브러리, 강력한 타이핑을 웹 애플리케이션 내에서 직접 활용하는 것을 상상해 보세요. 이 기사는 TinyGo를 중심으로 Go와 WebAssembly의 강력한 조합에 초점을 맞춰 이 비전을 현실로 만드는 실용적인 부분들을 파고듭니다.
핵심 개념: 기반이 되는 기둥 이해하기
코드를 자세히 살펴보기 전에 이 논의를 뒷받침하는 몇 가지 기본 개념을 명확히 해 봅시다.
- WebAssembly(Wasm): 스택 기반 가상 머신을 위한 이진 명령어 형식입니다. Wasm은 고수준 언어를 위한 이식 가능한 컴파일 대상이 되도록 설계되어 클라이언트 및 서버 애플리케이션을 위해 웹에서 배포할 수 있게 합니다. 거의 네이티브에 가까운 성능, 샌드박스 실행, JavaScript와의 상호 운용성을 제공합니다. 결정적으로, Wasm은 JavaScript 외의 언어로 작성된 코드를 브라우저에서 실행할 수 있는 방법을 제공합니다.
- Go(Golang): 단순성, 효율성, 내장 동시성 지원(고루틴 및 채널), 강력한 정적 타이핑으로 알려진 Google의 오픈 소스 프로그래밍 언어입니다. Go는 백엔드 서비스 및 명령줄 도구에 널리 사용되지만, 강력한 특성은 클라이언트 측 실행에 매력적인 후보가 됩니다.
- TinyGo: 마이크로컨트롤러, WebAssembly, 명령줄 도구를 위해 특별히 설계된 Go 컴파일러입니다. 표준 Go 컴파일러와 달리 TinyGo는 극도로 작은 바이너리를 생성하고 리소스가 제한된 환경에 최적화하는 데 중점을 둡니다. 이는 Go 코드를 WebAssembly로 컴파일하는 데 이상적인 선택이며, 더 작은 Wasm 모듈은 더 빠른 다운로드 시간과 향상된 사용자 경험으로 이어집니다.
시너지: Go, TinyGo, 그리고 WebAssembly
기존 Go 컴파일러는 WebAssembly로 컴파일할 수 있지만, 전체 Go 런타임이 포함되어 생성된 바이너리가 종종 상당히 큽니다. 이 지점에서 TinyGo가 빛을 발합니다. TinyGo의 전문 컴파일러는 불필요한 구성 요소를 제거하고 간결성을 최적화하여 생성된 Wasm 모듈의 크기를 크게 줄입니다. 이 최적화는 다운로드 크기가 성능에 직접적인 영향을 미치는 웹 애플리케이션에 중요합니다.
기본 원리는 다음을 포함합니다.
- Go 코드 작성.
- TinyGo를 사용하여 해당 Go 코드를 WebAssembly(.wasm) 파일로 컴파일.
- JavaScript를 사용하여 웹 브라우저에서 .wasm 파일을 로드하고 실행.
- Go(Wasm) 코드와 JavaScript 간의 통신 촉진.
실제 적용: 간단한 브라우저 애플리케이션
실제 예시로 설명해 보겠습니다. 사용자 입력을 받아 Wasm으로 컴파일된 Go 코드를 사용하여 처리하고 결과를 표시하는 간단한 웹 페이지입니다.
먼저 TinyGo가 설치되어 있는지 확인하세요. 공식 TinyGo 웹사이트(tinygo.org)의 지침을 따를 수 있습니다.
1. Go 모듈 (wasm_app/main.go):
package main import ( "fmt" "math" "strconv" "syscall/js" // JavaScript와의 상호 작용 활성화 ) func factorial(n int) int { if n == 0 { return 1 } return n * factorial(n-1) } func registerCallback() { js.Global().Set("calculateFactorial", js.FuncOf(func(this js.Value, args []js.Value) interface{} { if len(args) != 1 { return "Error: Expecting one argument (number to calculate factorial)." } input := args[0].String() num, err := strconv.Atoi(input) if err != nil { return fmt.Sprintf("Error: Invalid number input - %s", err.Error()) } if num < 0 { return "Error: Factorial not defined for negative numbers." } if num > 20 { // 데모를 위해 int 오버플로우 방지 return "Error: Input too large for simple factorial calculation." } result := factorial(num) return strconv.Itoa(result) })) } func main() { c := make(chan struct{}, 0) println("Go WebAssembly initialized!") registerCallback() <- c // Go 프로그램을 무기한 실행 유지 }
이 Go 코드에서:
factorial
함수를 정의합니다.syscall/js
패키지는 필수적입니다. Go가 JavaScript 런타임과 상호 작용할 수 있는 메커니즘을 제공합니다.js.Global().Set("calculateFactorial", js.FuncOf(...))
는 Go 함수를calculateFactorial
이라는 전역 JavaScript 함수로 노출합니다. 이 함수는 JavaScript 인수 배열을 받아 JavaScript가 해석할 수 있는interface{}
를 반환합니다.main
함수는 채널을 초기화하고 초기화 후 즉시 Go 프로그램이 종료되지 않도록 차단하여 JavaScript가 노출된 함수를 호출할 수 있도록 합니다.
2. Go를 WebAssembly로 컴파일:
터미널에서 wasm_app
디렉토리로 이동하여 실행합니다:
tinygo build -o wasm_app.wasm -target wasm ./main.go
이 명령은 WebAssembly 모듈인 wasm_app.wasm
을 생성합니다.
3. HTML (index.html):
<!DOCTYPE html> <html> <head> <title>Go WebAssembly Factorial</title> <style> body { font-family: sans-serif; margin: 20px; } #output { margin-top: 15px; padding: 10px; border: 1px solid #ccc; background-color: #f9f9f9; } </style> </head> <body> <h1>Go WebAssembly Factorial Calculator</h1> <input type="number" id="numberInput" placeholder="Enter a number (0-20)"> <button onclick="calculate()">Calculate Factorial</button> <div id="output"></div> <script src="wasm_exec.js"></script> <script> const go = new Go(); let wasm; WebAssembly.instantiateStreaming(fetch("wasm_app.wasm"), go.importObject).then((result) => { wasm = result.instance; go.run(wasm); document.getElementById("output").innerText = "WebAssembly module loaded. Enter a number to calculate factorial."; }).catch((err) => { document.getElementById("output").innerText = `Error loading WebAssembly: ${err}`; console.error(err); }); function calculate() { const inputElement = document.getElementById("numberInput"); const number = inputElement.value; if (number === "") { document.getElementById("output").innerText = "Please enter a number."; return; } try { // WebAssembly를 통해 노출된 Go 함수 호출 const result = calculateFactorial(number); document.getElementById("output").innerText = `Factorial of ${number} is: ${result}`; } catch (e) { document.getElementById("output").innerText = `Error from Go/Wasm: ${e}`; console.error("Error calling Go function:", e); } } </script> </body> </html>
4. wasm_exec.js
파일:
이 파일은 Go 배포판의 일부이며, 브라우저에서 Go로 생성된 WebAssembly를 실행하는 데 필요한 JavaScript 글루 코드(WebAssembly API 폴리필 및 Go 런타임 특정 사항 처리)를 제공합니다. Go 설치에서 복사해야 합니다.
Go가 설치되어 있다면 일반적으로 다음 위치에서 찾을 수 있습니다:
$(go env GOROOT)/misc/wasm/wasm_exec.js
이 파일을 index.html
및 wasm_app.wasm
과 동일한 디렉토리에 복사합니다.
5. 파일 서빙:
이 파일들을 서빙하려면 로컬 웹 서버가 필요합니다. 예를 들어 Python을 사용합니다:
python3 -m http.server
그런 다음 브라우저에서 http://localhost:8000
으로 엽니다. 인터페이스가 표시되고 로드 후 숫자를 입력하고 'Calculate Factorial'을 클릭할 수 있습니다. WebAssembly로 실행되는 Go 코드가 계산을 수행하고 결과를 반환합니다.
애플리케이션 시나리오:
- 성능에 민감한 계산: 복잡한 알고리즘 또는 데이터 처리가 네이티브에 가까운 속도로 클라이언트 측에서 수행되어야 할 때.
- 기존 Go 라이브러리 재사용: JavaScript로 완전히 다시 작성하지 않고도 확립된 Go 코드베이스 또는 알고리즘을 웹으로 이식.
- 게임 개발: 고성능 게임 로직을 Go로 작성하고 브라우저에서 렌더링.
- 풍부한 클라이언트 측 애플리케이션: Go의 타입 안전성과 동시성 기능이 상당한 이점을 제공하는 정교한 웹 애플리케이션 구축.
- 크로스 플랫폼 일관성: 데스크톱, 모바일 및 웹 애플리케이션 간에 Go로 작성된 핵심 애플리케이션 로직 공유.
결론: 웹 개발의 미래 포용하기
TinyGo로 촉진되는 Go와 WebAssembly의 조합은 웹 개발의 중요한 발전을 나타냅니다. Go 개발자가 브라우저로 범위를 확장하여 향상된 성능, 안정성 및 코드 관리 용이성을 갖춘 애플리케이션을 제공할 수 있도록 합니다. 아직 발전 중이지만, 이 기술은 강력하고 효율적인 웹 경험을 구축할 수 있는 흥미로운 가능성을 열어줍니다. Go와 WebAssembly는 클라이언트 측 개발을 위한 언어 선택이 더 이상 JavaScript에만 국한되지 않는 시대를 열어가며, 혁신과 기술적 우수성을 위한 새로운 길을 열어가고 있습니다.